Java程序不用像C++程序在程序中自行处理内存的回收释放。这是因为Java在JVM虚拟机上增加了垃圾回收(GC)机制,用以在合适的时间触发垃圾回收,将不需要的内存空间回收释放,避免无限制的内存增长导致的OOM。
回收前的准备
垃圾回收器在对堆进行回收前,第一件事情就是要确定这些对象哪些还“存活”,哪些已经“死去”。
引用计数算法
给对象中添加一个引用计数器,每当有一个地方引用它,计数器加1,当引用失效时,计数器值减1;任何时刻计数器为0的对象就是不可能在被使用的。它实现简单,判定效率高,但主流的Java虚拟机都不用它来管理内存,其中最主要的原因是它很难解决对象之间相互循环引用的问题。
可达性反洗算法
通过一系列称为GC Roots
的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为引用链,当一个对象到GC Roots
没有任何引用链相连,即从GC Roots
到这个对象不可达,则证明此对象是不可用的。即使有的对象相互关联,只要GC Roots
不可达,均被判定为可回收对象。这也是主流的实现方法。
Java中可作为GC Roots
的对象:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(即一般说的Native方法)引用的对象
对象的自救
在可达性算法中判断为不可达的对象时,它们不会立即被回收,真正宣告一个对象死亡,还需要判断该对象是否有必要执行finalize()
方法,当对象没有覆盖该方法,或者已经执行过该方法,虚拟机会将这两种情况视为没有必要执行。如果对象有必要执行该方法,该对象会被放在名为F-Queue
的队列中,虚拟机会建立一个低优先级的FInalizer
线程去执行该方法,并不保证等它运行结束,如果一个对象在finalize()
方法中执行缓慢,或者发生了死循环,很可能会导致F-Queue
队列一直等待。如果在执行中重新与引用链上的任何一个对象建立关联,如把自己复制给某个类变量或者对象的成员变量,那么就可以逃脱这一次回收。
回收算法
标记-清楚算法
首先标记出所有需要回收的对象, 在标记完成后统一回收。如图:
不足:标记和清除两个过程效率都不高;标记清除后产生大量不连续的内存碎片,导致以后运行的时候要分配较大对象时,无法找到足够的连续内存而不得不再一次垃圾回收。
复制算法
将内存按容量大小划分为大小相等的两块,每次使用其中一块,当这一块用完了,就将还存活着的对象复制到另一块上,然后再把已使用过的内存空间一次清理掉。
标记-整理算法
同标记清理算法一样,首先标记对象,但后续步骤不是直接清理,而是将所有存活的对象都向一端移动,然后直接清理掉边界以外的内存。
分代收集
Java堆主要分为新生代和老年代。对于新生代,每次收集都有大量对象死去,所以采用“复制算法”,将少量存货对象副职即可完成收集;对于老年代,其中的对象存活率高、没有额外空间对它们进行分配担保,必须使用“标记清除算法”或“标记整理算法”来收集。